//
// Project werewolf: basic_filter.cpp
// Created by alfielin on 2021/11/17.
//

#pragma once

#include "hv/EventLoopThread.h"
#include "g_constants.cpp"


namespace nz {

const char *const CODE = "code";
const char *const MSG = "msg";
const char *const DATA = "data";

static void
handle_exception(const char *errmsg, const std::shared_ptr<spdlog::logger> &logger, const HttpContextPtr &ctx) {
    ctx->response->status_code = HTTP_STATUS_INTERNAL_SERVER_ERROR;
    logger->error("[{}:{}][{} {}]=>[{} {}]",
                  ctx->request->client_addr.ip, ctx->request->port,
                  http_method_str(ctx->request->method), ctx->request->path.c_str(),
                  ctx->response->status_code, errmsg);
    ctx->set(CODE, -HTTP_STATUS_INTERNAL_SERVER_ERROR);
    ctx->set(MSG, errmsg);
}

static http_handler guard(const std::shared_ptr<spdlog::logger> &logger, http_handler &handler) {
    return [handler, &logger](const HttpContextPtr &ctx) noexcept {
        try {
            return handler(ctx);
        } catch (std::exception &e) {
            handle_exception(e.what(), logger, ctx);
        } catch (...) {
            handle_exception("Unknown C language exception", logger, ctx);
        }
        return int(HTTP_STATUS_INTERNAL_SERVER_ERROR);
    };
}

class BasicHandler {
public:

    static std::function<int(HttpRequest *, HttpResponse *)>
    preprocessor(const std::function<int(HttpRequest *, HttpResponse *)> &filter) {
        return [filter](HttpRequest *req, HttpResponse *resp) {
            int ret = filter(req, resp);
            return ret;
        };
    }

    static std::function<int(HttpRequest *, HttpResponse *)>
    postprocessor() {
        return [](HttpRequest *req, HttpResponse *resp) {
            if (req->content_type == APPLICATION_JSON &&
                !resp->json.contains(CODE) &&
                !resp->json.contains(MSG)) {
                resp->Set(CODE, 0);
                resp->Set(MSG, "OK");
            }
            return resp->status_code;
        };
    }

    static std::function<int(const HttpContextPtr &)>
    error_handler(const std::shared_ptr<spdlog::logger> &logger) {
        return [logger](const HttpContextPtr &ctx) {
            if (ctx->response->content_type == APPLICATION_JSON &&
                !ctx->response->json.contains(CODE) &&
                !ctx->response->json.contains(MSG)) {
                ctx->response->Set(CODE, -ctx->response->status_code);
                ctx->response->Set(MSG, http_status_str(ctx->response->status_code));
                logger->error("[{}:{}][{} {}]=>[{} {}]",
                              ctx->request->client_addr.ip, ctx->request->port,
                              http_method_str(ctx->request->method), ctx->request->path.c_str(),
                              ctx->response->status_code, http_status_str(ctx->response->status_code));
            }
            return ctx->response->status_code;
        };
    }

    static void guard_with_exception_handler(const std::shared_ptr<spdlog::logger> &logger,
                                             const std::shared_ptr<hv::HttpService> &router) {
        for (const auto &pp: router->api_handlers) {
            for (auto &handler: *pp.second) {
                logger->info("{}:{}", http_method_str(handler.method), pp.first);
                handler.handler = guard(logger, handler.handler);
            }
        }
    }

    static int basic_filter(HttpRequest *req, HttpResponse *resp, HttpFilterChain *chain) {
        // CORS
        resp->headers["Access-Control-Allow-Origin"] = "*";
        if (req->method == HTTP_OPTIONS) {
            resp->headers["Access-Control-Allow-Origin"] = req->GetHeader("Origin", "*");
            resp->headers["Access-Control-Allow-Methods"] = req->GetHeader("Access-Control-Request-Method",
                                                                           "OPTIONS, HEAD, GET, POST, PUT, DELETE, PATCH");
            resp->headers["Access-Control-Allow-Headers"] = req->GetHeader("Access-Control-Request-Headers",
                                                                           "Content-Type");
            return HTTP_STATUS_NO_CONTENT;
        }

        // Deserialize request body to json, form, etc.
        req->ParseBody();

        // Unified setting response Content-Type
        resp->content_type = APPLICATION_JSON;

        return chain->filter_next(req, resp);
    }

};


}
